package com.example.gulimall.ware.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.example.common.utils.R;
import com.example.gulimall.ware.entity.WareOrderTaskDetailEntity;
import com.example.gulimall.ware.entity.WareOrderTaskEntity;
import com.example.gulimall.ware.feign.OrderFeignService;
import com.example.gulimall.ware.service.WareOrderTaskDetailService;
import com.example.gulimall.ware.service.WareOrderTaskService;
import com.example.gulimall.ware.vo.OrderItemVo;
import com.example.gulimall.ware.vo.OrderVo;
import com.example.gulimall.ware.vo.SkuHasStockVo;
import com.example.gulimall.ware.vo.WareSkuLockVo;
import com.rabbitmq.client.Channel;
import exception.NoStockException;
import lombok.Data;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.common.utils.PageUtils;
import com.example.common.utils.Query;

import com.example.gulimall.ware.dao.WareSkuDao;
import com.example.gulimall.ware.entity.WareSkuEntity;
import com.example.gulimall.ware.service.WareSkuService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import to.mq.OrderTO;
import to.mq.StockDetailTo;
import to.mq.StockLockedTo;



@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Autowired
    RabbitTemplate rabbitTemplate;
    @Autowired
    WareOrderTaskService wareOrderTaskService;
    @Autowired
    WareOrderTaskDetailService wareOrderTaskDetailService;
    @Autowired
    OrderFeignService orderFeignService;
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<WareSkuEntity> page = this.page(
                new Query<WareSkuEntity>().getPage(params),
                new QueryWrapper<WareSkuEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public PageUtils queryPageCondition(Map<String, Object> params) {

        QueryWrapper<WareSkuEntity> wrapper = new QueryWrapper<WareSkuEntity>();
        if (params.containsKey("skuId")) {
            String skuId = params.get("skuId").toString();
            if (!StringUtils.isEmpty(skuId)) {
                wrapper.eq("sku_id", skuId);
            }
        }
        if (params.containsKey("wareId")) {
            String wareId = params.get("wareId").toString();
            if (!StringUtils.isEmpty(wareId)) {
                wrapper.eq("ware_id", wareId);
            }
        }
        IPage<WareSkuEntity> page = this.page(
                new Query<WareSkuEntity>().getPage(params),
                wrapper
        );
        return new PageUtils(page);
    }

    @Override
    public List<SkuHasStockVo> getSkuHasStock(List<Long> skuIds) {

        List<SkuHasStockVo> collect = skuIds.stream().map(skuId -> {
            SkuHasStockVo vo = new SkuHasStockVo();
            //查询当前sku的总库存
            Long count = baseMapper.getSkuHasStock(skuId);
            vo.setSkuId(skuId);
            vo.setHasStock(count == null ? false : count > 0);
            return vo;
        }).collect(Collectors.toList());
        return collect;
    }

    /**
     * 为某个订单锁定库存
     *   //有这个异常一定要回滚，其实不标注rollbackFor也会回滚，因为默认只要是运行时异常都会回滚
     * @param vo
     * @return
     * 库存解锁场景
     *  1 下单成功，订单过期没有支付被系统自动取消，被用户手动取消，都要解锁库存
     *  2 下单成功，库存锁定成功，接下来的业务调用失败，导致订单回滚，之前锁定的库存自动解锁
     */


     @Transactional(rollbackFor = NoStockException.class)
    @Override
    public boolean orderLockStock(WareSkuLockVo vo) {
         /**
          * 先保存库存工作单的详情
          * 追溯
          */
         WareOrderTaskEntity taskEntity = new WareOrderTaskEntity();
         taskEntity.setOrderSn(vo.getOrderSn());
         wareOrderTaskService.save(taskEntity);


        //1 按照下单的收货地址，找到一个就近仓库，锁定库存 现在不做这么复杂

        //1 找到每个商品在那个仓库都有库存
        List<OrderItemVo> locks = vo.getLocks();
        List<skuWareHasStock> collect = locks.stream().map(item -> {
            skuWareHasStock stock = new skuWareHasStock();
            Long skuId = item.getSkuId();
            stock.setSkuId(skuId);
            stock.setNum(item.getCount());
            //查询这个商品在哪里有库存
            List<Long> wareId = this.baseMapper.listWareIdHasStock(skuId);
            stock.setWareId(wareId);
            return stock;
        }).collect(Collectors.toList());
        boolean allLock = true;
        //2 锁定库存
        for (skuWareHasStock hasStock : collect) {
            boolean skuStocked = false;
            Long skuId = hasStock.getSkuId();
            List<Long> wareIds = hasStock.getWareId();
            if (wareIds == null || wareIds.size() == 0) {
                //没有任何仓库有这个商品的库存，直接抛出
                throw new NoStockException(skuId.toString());
            }
            // 1 如果每一个商品都锁定成功，将当前商品锁定了几件的工作单记录发给mq
            //2 如果锁定失败。前面保存的工作单信息就回滚了。发送出去消息也没问题，由于工作单信息回滚了，即使要解锁库存，由于数据库查不到id，就不用解锁
            //  其实2 是不合理的，因为如果只发了id ，如果有部分商品成功了，有部分失败了，但是工作单全部回滚了，但是库存扣减了，所有你就不知道他锁定了多少
            for (Long wareId : wareIds) {
                //挨个扣减仓库库存,成功返回1 失败返回0
                Long count = baseMapper.lockSkuStock(skuId, wareId, hasStock.getNum());
                if (count == 1) {
                    skuStocked = true;
                    //保存工作单详情
                    WareOrderTaskDetailEntity entity = new WareOrderTaskDetailEntity(null, skuId, "", hasStock.getNum(), taskEntity.getId(), wareId, 1);
                    wareOrderTaskDetailService.save(entity);
                    // TODO 告诉RabbiMQ库存锁定成功
                    StockLockedTo lockedTo = new StockLockedTo();
                    lockedTo.setId(taskEntity.getId());
                    StockDetailTo stockDetailTo = new StockDetailTo();
                    BeanUtils.copyProperties(entity,stockDetailTo);
                    //只发id不行，防止回滚以后找不到数据
                    lockedTo.setDetailTo(stockDetailTo);
                    rabbitTemplate.convertAndSend("stock-event-exchange","stock.locked",lockedTo);
                   // rabbitTemplate.
                    break;
                } else {
                    // 当前仓库，库存不足，锁失败

                }
                //如果当前商品等于false,当前商品所有仓库都没锁住
                if (skuStocked == false) {
                    throw new NoStockException(skuId.toString());
                }


            }
        }
        //能走到这，说明都是锁定成功的
        return true;

    }

    public   void  unLockStock(StockLockedTo to){

            System.out.println("收到解锁库存的消息");
            StockDetailTo detailTo = to.getDetailTo();
            Long skuId = detailTo.getSkuId();
            Long detailId = detailTo.getId();
            /**
             *   解锁
             *    1 查询数据库关于这个订单的锁定库存信息
             *    有：证明库存锁定成功
             *       解锁》：
             *          1 没有这个订单，必须解锁
             *          2 有这个订单，看情况是否解锁库存
             *              订单状态：已取消：解锁库存；没取消：不能解锁
             *    没有，库存锁定失败，库存回滚了，无需解锁
             */
            WareOrderTaskDetailEntity byId = wareOrderTaskDetailService.getById(detailId);
            if (byId!=null){
                Long id = to.getId();
                WareOrderTaskEntity taskEntity = wareOrderTaskService.getById(id);
                if (taskEntity==null){
                    return;
                }
                String orderSn = taskEntity.getOrderSn();
                R r = orderFeignService.getOrderStatus(orderSn);
                if (r.getCode()==0){
                    //订单数据返回成功

                    OrderVo data = r.getData(new TypeReference<OrderVo>() {
                    });
                    if (data==null||data.getStatus()==4){
                        //订单被取消，解锁库存
                        if (byId.getLockStatus()==1){
                            //当前库存工作单详情为11 已锁定但是未解锁才能解锁
                            unLockStock(detailTo.getSkuId(),detailTo.getWareId(),detailTo.getSkuNum(),detailId);

                        }

                    }
                }else {
                    //消息拒绝以后重新放到队列里面，让别人继续消费解锁
                    throw  new RuntimeException("远程服务失败"); //抛一个异常出去，让调用它的服务将消息重新放回队列
                }
            }else {
                //无需解锁

            }

       // baseMapper.unLockStock(skuId,wareId,num);
    }

    /**
     * 防止订单服务卡顿，导致订单状态消息一直改不了，库存消息优先到期，查询订单为新建状态，什么都不做就走了
     * 导致卡顿的订单，永远不能解锁库存
     * @param orderTO
     */
    @Transactional
    @Override
    public void unLockStock(OrderTO orderTO) {
        String orderSn = orderTO.getOrderSn();
        //查询最新的库存解锁状态，防止重复解锁库存
       WareOrderTaskEntity task= wareOrderTaskService.getorderTaskByOrderSn(orderSn);
        Long id = task.getId();
        //按照库存工作单找到所有，没有解锁的库存，进行解锁
        List<WareOrderTaskDetailEntity> entities = wareOrderTaskDetailService.list(new QueryWrapper<WareOrderTaskDetailEntity>()
                .eq("task_id", id)
                .eq("lock_status", 1));
        //解锁
        for (WareOrderTaskDetailEntity entity : entities) {
            unLockStock(entity.getSkuId(),entity.getWareId(),entity.getSkuNum(),entity.getId());
        }


    }

    /**
     * 解锁库存的方法
     * @param skuId
     * @param wareId
     * @param num
     * @param taskDetailId
     */
    public void unLockStock(Long skuId,Long wareId,Integer num,Long taskDetailId) {

        //库存解锁
        baseMapper.unLockStock(skuId,wareId,num);

        //更新工作单的状态
        WareOrderTaskDetailEntity taskDetailEntity = new WareOrderTaskDetailEntity(null,null,null,null,null,null,null);

        taskDetailEntity.setId(taskDetailId);
        //变为已解锁
        taskDetailEntity.setLockStatus(2);
        wareOrderTaskDetailService.updateById(taskDetailEntity);

    }

}
@Data
class skuWareHasStock{
    private Long skuId;
    private Integer num;
    private List<Long> wareId;
}